#pragma once
#include <glad/glad.h>
#include <vector>

class Buffer
{
protected:
  GLuint m_ID;
  size_t m_size = 0;
  uint32_t m_type;

public:
  Buffer(uint32_t type)
      : m_type(type)
  {
    glGenBuffers(1, &m_ID);
  }

  virtual ~Buffer()
  {
    glDeleteBuffers(1, &m_ID);
  }

  bool IsValid() const { return m_ID != 0; }

  void Bind() const
  {
    glBindBuffer(m_type, m_ID);
  }

  void Unbind() const
  {
    glBindBuffer(m_type, 0);
  }

  template<typename T>
  void BufferStaticData(const std::vector<T> &data, int32_t dataSize, int32_t dataOffset = 0) {
    assert(IsValid() && "Cannot write data to an invalid buffer");
    assert(dataOffset + dataSize <= data.size() && "Data is out of bounds");

    Bind();
    m_size = dataSize;
    glBufferData(m_type, sizeof(T) * m_size, &data[dataOffset], GL_STATIC_DRAW);
  }

  template<typename T>
  void BufferStaticData(const std::vector<T> &data) {
    BufferStaticData(data, data.size());
  }

  template<typename T>
  void BufferDynamicData(const std::vector<T> &data, int32_t dataSize, int32_t dataOffset = 0) {
    assert(IsValid() && "Cannot write data to an invalid buffer");
    assert(dataOffset + dataSize <= data.size() && "Data is out of bounds");

    Bind();
    m_size = dataSize;
    glBufferData(m_type, sizeof(T) * m_size, &data[dataOffset], GL_DYNAMIC_DRAW);
  }

  template<typename T>
  void BufferDynamicData(const std::vector<T> &data) {
    BufferDynamicData(data, data.size());
  }

  template<typename T>
  void BufferDynamicSubData(const std::vector<T> &data,
                            int32_t dataSize,
                            int32_t dataOffset = 0,
                            int32_t bufferOffset = 0) {
    assert(IsValid() && "Cannot write data to an invalid buffer");
    assert(dataOffset + dataSize <= data.size() && "Data is out of bounds");
    assert(dataOffset + dataSize <= m_size && "Buffer is out of bounds");

    Bind();
    m_size = dataSize;
    glBufferSubData(m_type, bufferOffset * sizeof(T), sizeof(T) * m_size, &data[dataOffset]);
  }

  inline size_t GetSize() const { return m_size; }


  Buffer(const Buffer &) = delete;
  Buffer(Buffer &) = delete;
  Buffer(Buffer &&) noexcept = delete;
  Buffer &operator=(Buffer &) = delete;
  Buffer &operator=(Buffer &&) noexcept = delete;

};